import { assign, forEach, isArray, every } from 'min-dash';

import { is } from 'bpmn-js/lib/util/ModelUtil';

import { isExpanded, isEventSubProcess } from 'bpmn-js/lib/util/DiUtil';

import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';

import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';

import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';

/**
 * A provider for BPMN 2.0 elements context pad
 */
export default function ContextPadProvider(
	config,
	injector,
	eventBus,
	contextPad,
	modeling,
	elementFactory,
	connect,
	create,
	popupMenu,
	canvas,
	rules,
	translate
) {
	config = config || {};

	contextPad.registerProvider(this);

	this._contextPad = contextPad;

	this._modeling = modeling;

	this._elementFactory = elementFactory;
	this._connect = connect;
	this._create = create;
	this._popupMenu = popupMenu;
	this._canvas = canvas;
	this._rules = rules;
	this._translate = translate;

	if (config.autoPlace !== false) {
		this._autoPlace = injector.get('autoPlace', false);
	}

	eventBus.on('create.end', 250, function (event) {
		let context = event.context,
			shape = context.shape;

		if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
			return;
		}

		let entries = contextPad.getEntries(shape);

		if (entries.replace) {
			entries.replace.action.click(event, shape);
		}
	});
}

ContextPadProvider.$inject = [
	'config.contextPad',
	'injector',
	'eventBus',
	'contextPad',
	'modeling',
	'elementFactory',
	'connect',
	'create',
	'popupMenu',
	'canvas',
	'rules',
	'translate'
];

ContextPadProvider.prototype.getMultiElementContextPadEntries = function (elements) {
	let modeling = this._modeling;

	let actions = {};

	if (this._isDeleteAllowed(elements)) {
		assign(actions, {
			delete: {
				group: 'edit',
				className: 'bpmn-icon-trash',
				title: this._translate('Remove'),
				action: {
					click: function (event, elements) {
						modeling.removeElements(elements.slice());
					}
				}
			}
		});
	}

	return actions;
};

/**
 * @param {djs.model.Base[]} elements
 * @return {boolean}
 */
ContextPadProvider.prototype._isDeleteAllowed = function (elements) {
	let baseAllowed = this._rules.allowed('elements.delete', {
		elements: elements
	});

	if (isArray(baseAllowed)) {
		return every(baseAllowed, function (element) {
			return includes(baseAllowed, element);
		});
	}

	return baseAllowed;
};

ContextPadProvider.prototype.getContextPadEntries = function (element) {
	let contextPad = this._contextPad,
		modeling = this._modeling,
		elementFactory = this._elementFactory,
		connect = this._connect,
		create = this._create,
		popupMenu = this._popupMenu,
		rules = this._rules,
		autoPlace = this._autoPlace,
		translate = this._translate;

	let actions = {};

	if (element.type === 'label') {
		return actions;
	}

	let businessObject = element.businessObject;

	function startConnect(event, element) {
		connect.start(event, element);
	}

	function removeElement(e, element) {
		modeling.removeElements([element]);
	}

	function getReplaceMenuPosition(element) {
		let Y_OFFSET = 5;

		let pad = contextPad.getPad(element).html;

		let padRect = pad.getBoundingClientRect();

		let pos = {
			x: padRect.left,
			y: padRect.bottom + Y_OFFSET
		};

		return pos;
	}

	/**
	 * Create an append action
	 *
	 * @param {string} type
	 * @param {string} className
	 * @param {string} [title]
	 * @param {Object} [options]
	 *
	 * @return {Object} descriptor
	 */
	function appendAction(type, className, title, options) {
		if (typeof title !== 'string') {
			options = title;
			title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
		}

		function appendStart(event, element) {
			let shape = elementFactory.createShape(assign({ type: type }, options));
			create.start(event, shape, {
				source: element
			});
		}

		let append = autoPlace
			? function (event, element) {
					let shape = elementFactory.createShape(assign({ type: type }, options));

					autoPlace.append(element, shape);
				}
			: appendStart;

		return {
			group: 'model',
			className: className,
			title: title,
			action: {
				dragstart: appendStart,
				click: append
			}
		};
	}

	function splitLaneHandler(count) {
		return function (event, element) {
			// actual split
			modeling.splitLane(element, count);

			// refresh context pad after split to
			// get rid of split icons
			contextPad.open(element, true);
		};
	}

	if (isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) && isExpanded(element)) {
		let childLanes = getChildLanes(element);

		assign(actions, {
			'lane-insert-above': {
				group: 'lane-insert-above',
				className: 'bpmn-icon-lane-insert-above',
				title: translate('Add Lane above'),
				action: {
					click: function (event, element) {
						modeling.addLane(element, 'top');
					}
				}
			}
		});

		if (childLanes.length < 2) {
			if (element.height >= 120) {
				assign(actions, {
					'lane-divide-two': {
						group: 'lane-divide',
						className: 'bpmn-icon-lane-divide-two',
						title: translate('Divide into two Lanes'),
						action: {
							click: splitLaneHandler(2)
						}
					}
				});
			}

			if (element.height >= 180) {
				assign(actions, {
					'lane-divide-three': {
						group: 'lane-divide',
						className: 'bpmn-icon-lane-divide-three',
						title: translate('Divide into three Lanes'),
						action: {
							click: splitLaneHandler(3)
						}
					}
				});
			}
		}

		assign(actions, {
			'lane-insert-below': {
				group: 'lane-insert-below',
				className: 'bpmn-icon-lane-insert-below',
				title: translate('Add Lane below'),
				action: {
					click: function (event, element) {
						modeling.addLane(element, 'bottom');
					}
				}
			}
		});
	}

	if (is(businessObject, 'bpmn:FlowNode')) {
		if (is(businessObject, 'bpmn:EventBasedGateway')) {
			assign(actions, {
				'append.receive-task': appendAction('bpmn:ReceiveTask', 'bpmn-icon-receive-task', translate('Append ReceiveTask')),
				'append.message-intermediate-event': appendAction(
					'bpmn:IntermediateCatchEvent',
					'bpmn-icon-intermediate-event-catch-message',
					translate('Append MessageIntermediateCatchEvent'),
					{ eventDefinitionType: 'bpmn:MessageEventDefinition' }
				),
				'append.timer-intermediate-event': appendAction(
					'bpmn:IntermediateCatchEvent',
					'bpmn-icon-intermediate-event-catch-timer',
					translate('Append TimerIntermediateCatchEvent'),
					{ eventDefinitionType: 'bpmn:TimerEventDefinition' }
				),
				'append.condition-intermediate-event': appendAction(
					'bpmn:IntermediateCatchEvent',
					'bpmn-icon-intermediate-event-catch-condition',
					translate('Append ConditionIntermediateCatchEvent'),
					{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
				),
				'append.signal-intermediate-event': appendAction(
					'bpmn:IntermediateCatchEvent',
					'bpmn-icon-intermediate-event-catch-signal',
					translate('Append SignalIntermediateCatchEvent'),
					{ eventDefinitionType: 'bpmn:SignalEventDefinition' }
				)
			});
		} else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
			assign(actions, {
				'append.compensation-activity': appendAction('bpmn:Task', 'bpmn-icon-task', translate('Append compensation activity'), {
					isForCompensation: true
				})
			});
		} else if (
			!is(businessObject, 'bpmn:EndEvent') &&
			!businessObject.isForCompensation &&
			!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
			!isEventSubProcess(businessObject)
		) {
			assign(actions, {
				'append.end-event': appendAction('bpmn:EndEvent', 'bpmn-icon-end-event-none', translate('Append EndEvent')),
				'append.gateway': appendAction('bpmn:ExclusiveGateway', 'bpmn-icon-gateway-none', translate('Append Gateway')),
				'append.append-user-task': appendAction('bpmn:UserTask', 'bpmn-icon-user-task', '添加用户任务'),
				'append.intermediate-event': appendAction(
					'bpmn:IntermediateThrowEvent',
					'bpmn-icon-intermediate-event-none',
					translate('Append Intermediate/Boundary Event')
				)
			});
		}
	}

	if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
		// Replace menu entry
		assign(actions, {
			replace: {
				group: 'edit',
				className: 'bpmn-icon-screw-wrench',
				title: translate('Change type'),
				action: {
					click: function (event, element) {
						let position = assign(getReplaceMenuPosition(element), {
							cursor: { x: event.x, y: event.y }
						});

						popupMenu.open(element, 'bpmn-replace', position, {
							title: translate('Change element'),
							width: 300,
							search: true
						});
					}
				}
			}
		});
	}

	if (is(businessObject, 'bpmn:SequenceFlow')) {
		assign(actions, {
			'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
		});
	}

	if (isAny(businessObject, ['bpmn:FlowNode', 'bpmn:InteractionNode', 'bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
		assign(actions, {
			'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'),

			connect: {
				group: 'connect',
				className: 'bpmn-icon-connection-multi',
				title: translate('Connect using ' + (businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') + 'Association'),
				action: {
					click: startConnect,
					dragstart: startConnect
				}
			}
		});
	}

	if (is(businessObject, 'bpmn:TextAnnotation')) {
		assign(actions, {
			connect: {
				group: 'connect',
				className: 'bpmn-icon-connection-multi',
				title: translate('Connect using Association'),
				action: {
					click: startConnect,
					dragstart: startConnect
				}
			}
		});
	}

	if (isAny(businessObject, ['bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
		assign(actions, {
			connect: {
				group: 'connect',
				className: 'bpmn-icon-connection-multi',
				title: translate('Connect using DataInputAssociation'),
				action: {
					click: startConnect,
					dragstart: startConnect
				}
			}
		});
	}

	if (is(businessObject, 'bpmn:Group')) {
		assign(actions, {
			'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
		});
	}

	// delete element entry, only show if allowed by rules
	let deleteAllowed = rules.allowed('elements.delete', { elements: [element] });

	if (isArray(deleteAllowed)) {
		// was the element returned as a deletion candidate?
		deleteAllowed = deleteAllowed[0] === element;
	}

	if (deleteAllowed) {
		assign(actions, {
			delete: {
				group: 'edit',
				className: 'bpmn-icon-trash',
				title: translate('Remove'),
				action: {
					click: removeElement
				}
			}
		});
	}

	return actions;
};

// helpers
function isEventType(eventBo, type, definition) {
	let isType = eventBo.$instanceOf(type);
	let isDefinition = false;

	let definitions = eventBo.eventDefinitions || [];
	forEach(definitions, function (def) {
		if (def.$type === definition) {
			isDefinition = true;
		}
	});

	return isType && isDefinition;
}

function includes(array, item) {
	return array.indexOf(item) !== -1;
}
